import java.util.HashMap;
import java.util.Map;

public class AISParser18 {

    public static String asciiTo6bit(int asciiCode) throws IllegalArgumentException {
        // Range checks
        if (asciiCode < 0x30 || asciiCode > 0x77) {
            throw new IllegalArgumentException("Data error: ASCII code " + asciiCode + " out of range");
        }
        if (0x58 <= asciiCode && asciiCode <= 0x59) {
            throw new IllegalArgumentException("Data error: ASCII code " + asciiCode + " in reserved range");
        }

        // Numerical calculation
        int temp = asciiCode + 0x28;
        if (temp > 0x80) {
            temp += 0x20;
        } else {
            temp += 0x28;
        }

        // Extract lower 6 bits
        int binary6bit = temp & 0x3F;

        // Return 6-bit binary string
        return String.format("%6s", Integer.toBinaryString(binary6bit)).replace(' ', '0');
    }

    public static String stringTo168bit(String inputString) throws IllegalArgumentException {
        // Check input length is 28 characters
        if (inputString.length() != 28) {
            throw new IllegalArgumentException("Input string must be 28 characters long, current length is " + inputString.length());
        }

        // Convert each character to 6-bit binary
        StringBuilder combinedBits = new StringBuilder();
        for (char c : inputString.toCharArray()) {
            int asciiCode = (int) c;
            String bit6 = asciiTo6bit(asciiCode);
            combinedBits.append(bit6);
        }

        // Check we have at least 168 bits
        if (combinedBits.length() < 168) {
            throw new IllegalArgumentException("Converted binary bits are less than 168 bits");
        }

        return combinedBits.substring(0, 168);
    }

    public static Map<String, Map<String, Object>> parseAisMessage(String binaryMessage) throws IllegalArgumentException {
        // Check message length
        if (binaryMessage.length() != 168) {
            throw new IllegalArgumentException("AIS message must be 168 bits, current length is " + binaryMessage.length());
        }

        Map<String, Map<String, Object>> fields = new HashMap<>();

        // 1. Message ID
        Map<String, Object> messageId = new HashMap<>();
        messageId.put("binary", binaryMessage.substring(0, 6));
        messageId.put("value", Integer.parseInt(binaryMessage.substring(0, 6), 2));
        messageId.put("description", "Message type identifier");
        fields.put("message_id", messageId);

        // 2. Repeat Indicator
        Map<String, Object> repeatIndicator = new HashMap<>();
        repeatIndicator.put("binary", binaryMessage.substring(6, 8));
        repeatIndicator.put("value", Integer.parseInt(binaryMessage.substring(6, 8), 2));
        repeatIndicator.put("description", "Controls message retransmission");
        fields.put("repeat_indicator", repeatIndicator);

        // 3. User ID/MMSI
        Map<String, Object> userId = new HashMap<>();
        userId.put("binary", binaryMessage.substring(8, 38));
        userId.put("value", Integer.parseInt(binaryMessage.substring(8, 38), 2));
        userId.put("description", "MMSI number (ship unique identifier)");
        fields.put("user_id", userId);

        // 4. reserve 备用
        Map<String, Object> reserve = new HashMap<>();
        reserve.put("binary", binaryMessage.substring(38, 46));
        reserve.put("value", Integer.parseInt(binaryMessage.substring(38, 46), 2));
        reserve.put("description", "MMSI number (ship unique identifier)");
        fields.put("reserve", reserve);


        // 5. sog地面航速
        Map<String, Object> sog = new HashMap<>();
        sog.put("binary", binaryMessage.substring(46, 56));
        sog.put("value", Integer.parseInt(binaryMessage.substring(46, 56), 2));
        sog.put("description", "MMSI number (ship unique identifier)");
        fields.put("sog", sog);

        // 6. 位置准确度
        Map<String, Object> accuracy = new HashMap<>();
        accuracy.put("binary", binaryMessage.substring(56, 57));
        accuracy.put("value", Integer.parseInt(binaryMessage.substring(56, 57), 2));
        accuracy.put("description", "MMSI number (ship unique identifier)");
        fields.put("accuracy", accuracy);

        // 7. Longitude
        String lonBits = binaryMessage.substring(57, 85);
        int lonValue = Integer.parseInt(lonBits, 2);
        if (lonValue >= (1 << 27)) {
            lonValue = lonValue - (1 << 28);
        }
        double longitude = lonValue / 600000.0;
        Map<String, Object> longitudeField = new HashMap<>();
        longitudeField.put("binary", lonBits);
        longitudeField.put("value", lonValue);
        longitudeField.put("description", "Ship longitude coordinate: " + longitude + "° " + (longitude >= 0 ? "East" : "West"));
        fields.put("longitude", longitudeField);

        // 8. Latitude
        String latBits = binaryMessage.substring(85, 112);
        int latValue = Integer.parseInt(latBits, 2);
        if (latValue >= (1 << 26)) {
            latValue = latValue - (1 << 27);
        }
        double latitude = latValue / 600000.0;
        Map<String, Object> latitudeField = new HashMap<>();
        latitudeField.put("binary", latBits);
        latitudeField.put("value", latValue);
        latitudeField.put("description", "Ship latitude coordinate: " + latitude + "° " + (latitude >= 0 ? "North" : "South"));
        fields.put("latitude", latitudeField);


        // 9. Course over ground (COG)
        int cogValue = Integer.parseInt(binaryMessage.substring(112, 124), 2);
        String cogDescription;
        if (cogValue == 3600) {
            cogDescription = "Not available";
        } else {
            cogDescription = "Course over ground: " + (cogValue / 10.0) + "°";
        }
        Map<String, Object> courseOverGround = new HashMap<>();
        courseOverGround.put("binary", binaryMessage.substring(112, 124));
        courseOverGround.put("value", cogValue);
        courseOverGround.put("description", cogDescription);
        fields.put("course_over_ground", courseOverGround);


        // 10. True heading 实际航向
        Map<String, Object> trueHeading = new HashMap<>();
        trueHeading.put("binary", binaryMessage.substring(124, 133));
        trueHeading.put("value", Integer.parseInt(binaryMessage.substring(124, 133), 2));
        trueHeading.put("description", "true_heading: " + Integer.parseInt(binaryMessage.substring(124, 133), 2) + "°");
        fields.put("true_heading", trueHeading);

        // 10. 时戳
        Map<String, Object> timestamp = new HashMap<>();
        timestamp.put("binary", binaryMessage.substring(133, 139));
        timestamp.put("value", Integer.parseInt(binaryMessage.substring(133, 139), 2));
        timestamp.put("description", "timestamp: " + Integer.parseInt(binaryMessage.substring(133, 139), 2) );
        fields.put("timestamp", timestamp);

        return fields;
    }

    public static void printAisFields(Map<String, Map<String, Object>> fields) {
        System.out.println("AIS Message Parsing Results:");
        for (Map.Entry<String, Map<String, Object>> entry : fields.entrySet()) {
            String fieldName = entry.getKey();
            Map<String, Object> data = entry.getValue();

            // Convert snake_case to Title Case
            String[] words = fieldName.split("_");
            StringBuilder titleCase = new StringBuilder();
            for (String word : words) {
                titleCase.append(Character.toUpperCase(word.charAt(0)))
                        .append(word.substring(1))
                        .append(" ");
            }

            System.out.println("\n" + titleCase.toString().trim() + ":");
            System.out.println("  Binary: " + data.get("binary"));
            System.out.println("  Value: " + data.get("value"));
            System.out.println("  Description: " + data.get("description"));
        }
    }

    public static void main(String[] args) {
        String testString = "B6:d9p0002;29<4L022F;wlV3P06";

        try {
            // Convert string to 168-bit binary
            String binaryMessage = stringTo168bit(testString);
            System.out.println("Input string: " + testString);
            System.out.println("Converted 168-bit binary: " + binaryMessage);
            System.out.println("Binary length: " + binaryMessage.length());

            // Parse AIS message
            Map<String, Map<String, Object>> aisFields = parseAisMessage(binaryMessage);

            // Print parsing results
            printAisFields(aisFields);

        } catch (IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}